package org.gpp.proj1.communication.manager;

import java.rmi.RemoteException;

import org.gpp.proj1.communication.CommunicationException;
import org.gpp.proj1.communication.ConnectionException;
import org.gpp.proj1.communication.listener.OfflineClientListener;
import org.gpp.proj1.communication.listener.RequestListener;
import org.gpp.proj1.communication.processor.RMIRequestProcessor;
import org.gpp.proj1.communication.protocol.Message;
import org.gpp.proj1.communication.sender.RMIRequestSender;
import org.gpp.proj1.communication.sender.RequestSenderIF;
import org.gpp.proj1.communication.sender.manager.RMIRequestSenderManager;
import org.gpp.proj1.communication.sender.manager.RequestSenderManagerIF;

import com.jme.util.LoggingSystem;

/**
 * Class that establishes a channel between peers. The
 * messages are sent as a broadcast to all of the
 * client peers, whereas incoming messages are forwarded
 * to listeners.
 * 
 * The manager also scans the currently connected clients list
 * for offline clients (all failures are considered permanent). It
 * also starts a request processor when initialized, listening on
 * the given port.
 *  
 * @author Paolo Victor, paolovictor@gmail.com
 */
public class RMINetworkManager implements NetworkManager {

	/**
	 * The request processor that is initialized by the
	 * manager during startup
	 */
	protected RMIRequestProcessor requestProcessor;

	/**
	 * Class that holds the request senders that represents outgoing connections
	 * to peers. It's also used by the request processor to check if a
	 * peer that's sending a message is connected to this peer. Only connected
	 * peers may send messages. 
	 */
	protected RequestSenderManagerIF requestSenderManager;

	/**
	 * The request processor port
	 */
	private final int port;

	/**
	 * Builds and starts a network manager, initializing a request
	 * processor listening on the given port.
	 * 
	 * @param port
	 * @throws CommunicationException
	 * @throws RemoteException
	 */
	public RMINetworkManager( int port ) throws CommunicationException, RemoteException {

		this.port = port;
		this.requestSenderManager = new RMIRequestSenderManager();

		try {		
			this.requestProcessor = new RMIRequestProcessor( requestSenderManager, port );
			this.requestProcessor.startup();
		} catch( RemoteException exception ) {
			this.requestSenderManager.stop();
		}
	}


	public void broadcastMessage( Message message ) {

		boolean messageFailed = false;

		// Sends the message to all the request senders. If a message can't be
		// delivered, the unresponsive client thread is notified
		synchronized ( this.requestSenderManager ) {
			for( RequestSenderIF sender : this.requestSenderManager.getSenders() ) {
				try {
					sender.send( message );
				} catch (ConnectionException e) {
					messageFailed = true;

					LoggingSystem.getLogger().warning( "Could not send message to \"" + sender.getServerId() + "\"" );
					LoggingSystem.getLogger().throwing( this.getClass().getName(), "sendMessage", e );
				}
			}
		}

		// If some message could not be sent, notify the request sender
		// manager to search for offline clients
		if( messageFailed ) {
			this.requestSenderManager.notifyUndeliveredMessage();
		}
	}

	/* (non-Javadoc)
	 * @see org.gpp.proj1.communication.manager.NetworkManager#registerRequestListener(org.gpp.proj1.communication.listener.RequestListener)
	 */
	public void registerRequestListener(RequestListener listener) {

		this.requestProcessor.registerListener( listener );
	}

	/* (non-Javadoc)
	 * @see org.gpp.proj1.communication.manager.NetworkManager#registerOfflineClientListener(org.gpp.proj1.communication.listener.OfflineClientListener)
	 */
	public void registerOfflineClientListener(OfflineClientListener listener) {

		this.requestSenderManager.registerOfflineClientListener( listener );
	}

	/* (non-Javadoc)
	 * @see org.gpp.proj1.communication.manager.NetworkManager#stop()
	 */
	public void stop() {

		// The processor notifies the sender manager, that disconnects all the senders
		this.requestProcessor.stop();
	}


	/* (non-Javadoc)
	 * @see org.gpp.proj1.communication.manager.NetworkManager#connectTo(java.lang.String, int)
	 */
	public String connectTo(String serverHost, int serverPort) throws ConnectionException {

		RequestSenderIF sender = RMIRequestSender.connectToServer( this.requestSenderManager, port, serverHost, serverPort );

		return sender.getServerId();
	}


	/* (non-Javadoc)
	 * @see org.gpp.proj1.communication.manager.NetworkManager#sendMessage(java.lang.String, org.gpp.proj1.communication.protocol.Message)
	 */
	public void sendMessage(String receiverID, Message message) {
		// Sends the message to all the request senders. If a message can't be
		// delivered, the unresponsive client thread is notified
		synchronized ( this.requestSenderManager ) {
			if( ! this.requestSenderManager.contains( receiverID ) ) {
				LoggingSystem.getLogger().warning( "Client not online: \"" + receiverID + "\"" );
				return;
			}

			try {
				this.requestSenderManager.getSender( receiverID ).send( message );
			} catch (ConnectionException e) {
				LoggingSystem.getLogger().warning( "Could not send message to \"" + receiverID + "\"" );
				LoggingSystem.getLogger().throwing( this.getClass().getName(), "sendMessage", e );

				// If some message could not be sent, notify the request sender
				// manager to search for offline clients
				this.requestSenderManager.notifyUndeliveredMessage();
			}
		}
	}


	/* (non-Javadoc)
	 * @see org.gpp.proj1.communication.manager.NetworkManager#disconnectClient(java.lang.String)
	 */
	public void disconnectClient(String clientId) {

		synchronized ( this.requestSenderManager ) {
			if( ! this.requestSenderManager.contains( clientId ) ) {
				LoggingSystem.getLogger().warning( "Client not online: \"" + clientId + "\"" );
				return;
			}

			try{
				RequestSenderIF sender = this.requestSenderManager.getSender( clientId );
				sender.disconnect();
			} catch (ConnectionException e) {
				LoggingSystem.getLogger().warning( "Could not disconnect to \"" + clientId + "\"" );
				LoggingSystem.getLogger().throwing( this.getClass().getName(), "disconnectClient", e );
			}
		}
	}


	/* (non-Javadoc)
	 * @see org.gpp.proj1.communication.manager.NetworkManager#unregisterRequestListener(org.gpp.proj1.communication.listener.RequestListener)
	 */
	public void unregisterRequestListener(RequestListener listener) {

		this.requestProcessor.unregisterListener( listener );
	}


	/* (non-Javadoc)
	 * @see org.gpp.proj1.communication.manager.NetworkManager#getClientIdFor(java.lang.String)
	 */
	public String getClientIdFor(String serverId) throws CommunicationException {
		
		if( this.requestSenderManager.contains( serverId ) ) {
			return this.requestSenderManager.getSender( serverId ).getClientId();
 		} else {
 			throw new CommunicationException( "Not connected" );
 		}
	}
}
